#include <string>
#include <sstream>
#include <map>
#include <dirent.h>
#include <sys/stat.h>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <unistd.h>
#include "discovery_service.h"
#include "softbus_client_interface.h"
#include "napi/native_api.h"
#include "hilog/log.h"

using namespace std;

// Context of eTS callback function
static napi_env envRef;
// ETS callback function pointer
static napi_ref callbackRef;
NodeBasicInfo *dev;

static void closeAllSession();

static void sessionDisconnect(int stepNum);

static void createImagesSavePath();

static string getConnectedDeviceInfo(DeviceDetail *deviceDetails, int arrayLength);

/**
 * The initialization function before the connection is established,
 * including the creation of sessionServer, the release of soft bus capabilities,
 * and the discovery of devices under the same LAN.
 *
 * @return Result of initialization
 */
static string sessionServerInit(callBack *func)
{
    int ret;

    // Create sessionServer
    ret = CreateSessionServerInterface(func);
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] <CreateSessionServerInterface> failed, ret=%d", ret);
        return "<CreateSessionServerInterface> failed";
    }

    // Publish Service
    ret = PublishServiceInterface();
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] <PublishServiceInterface> failed");
        sessionDisconnect(4);
        return "<PublishServiceInterface> failed";
    }

    // Discovery devices
    ret = DiscoveryInterface();
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] <DiscoveryInterface> failed");
        sessionDisconnect(3);
        return "<DiscoveryInterface> failed";
    }

    createImagesSavePath();

    OH_LOG_INFO(LOG_APP, "[INFO] Session server init success!!!");
    return "[INFO] Session server init success";
}

/**
 * Discover the devices in the network and establish a session with the device to be connected
 * @return Device information for establishing connection
 */
static string sessionConnect()
{

    // Re detection equipment
    StopDiscoveryInterface();
    DiscoveryInterface();

    // Discover devices in the network
    dev = nullptr;
    int devNum = GetAllNodeDeviceInfoInterface(&dev);
    if (devNum <= 0)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] <GetAllNodeDeviceInfoInterface> failed");
        return "<GetAllNodeDeviceInfoInterface> failed";
    }

    DeviceDetail deviceDetails[devNum];
    int connectNum = GetDeviceDetailInterface(deviceDetails, dev, devNum);
    OH_LOG_INFO(LOG_APP, "[INFO] Connected device count: %{public}d", connectNum);

    

    return getConnectedDeviceInfo(deviceDetails, connectNum);
}

/***
 * Called when the device connection fails, or when the connection is manually closed, to release resources
 * @param stepNum Failed Steps
 * @param sessionId The session ID to be closed is - 1 by default, indicating that the parameter cannot be used.
 */
static void sessionDisconnect(int stepNum)
{
    switch (stepNum)
    {
    case 0:
        // Close session connection
        closeAllSession();
        OH_LOG_INFO(LOG_APP, "[INFO] Session close end!!!");
    case 1:
        // Free node information
        FreeNodeInfoInterface(dev);
        OH_LOG_INFO(LOG_APP, "[INFO] Node free end!!!");
    case 2:
        // Stop detection
        StopDiscoveryInterface();
        OH_LOG_INFO(LOG_APP, "[INFO] Discovery stop end!!!");
    case 3:
        // Stop serivce publish
        UnPublishServiceInterface();
        OH_LOG_INFO(LOG_APP, "[INFO] Service UnPublish end!!!");
    case 4:
        // close session server
        RemoveSessionServerInterface();
        OH_LOG_INFO(LOG_APP, "[INFO] SessionServer remove end!!!");
        break;
    default:
        // When you do not know which step to execute, close all.
        closeAllSession();
        FreeNodeInfoInterface(dev);
        StopDiscoveryInterface();
        UnPublishServiceInterface();
        RemoveSessionServerInterface();
    }
}

/**
 * Sending messages via distributed soft bus
 * @param sessionId Session ID of the peer device
 * @param data Data sent
 * @param dataLength Length of sent data
 * @return Whether the sending is successful. If 0 is returned, it means success. Otherwise, it means failure
 */
static int sendData(const char *peerSessionName, const char *data, unsigned int dataLength)
{
    OH_LOG_INFO(LOG_APP, "[HP_DEBUG] begin send data[%{public}s] to [%{public}s]", data);
    int sessionId = sessionNameAndId[peerSessionName];
    if (sessionId <= 0 || !data || dataLength <= 0)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] peerSessionName[%{public}s], sessionId[%{public}d], data[%{public}s], dataLength[%{public}d] is not available",peerSessionName, sessionId, data, dataLength);
        return -1;
    }
    // OH_LOG_INFO(LOG_APP, "[HP_DEBUG] begin send data[%{public}s] to [%{public}s], after check.", data);
    int ret = SendBytesInterface(sessionId, data, dataLength);
    if (ret)
    {
        OH_LOG_ERROR(LOG_APP, "[ERROR] Failed to send data to sessionId: %{public}d Return value %{public}d ", sessionId, ret);
        return ret;
    }
    OH_LOG_INFO(LOG_APP, "[INFO] Successfully send data to the device of : %{public}s", peerSessionName);
    return ret;
}

/**
 * @brief Close all session
 * 
 */
static void closeAllSession()
{
    map<string, int>::iterator it;
    map<string, int>::iterator itEnd;
    it = sessionNameAndId.begin();
    itEnd = sessionNameAndId.end();
    while (it != itEnd)
    {
        CloseSessionInterface(it->second);
        OH_LOG_INFO(LOG_APP, "[INFO] sessionId: %{public}d closed ", it->second);
        it++;
    }
}

/**
 * @brief Create a Images Save Path object
 * 
 */
static void createImagesSavePath()
{
    OH_LOG_INFO(LOG_APP, "[INFO] Start to create images save path");
    string savePath[2] = {{PICTURE_SAVE_PATH + "ai_analysis"}, {PICTURE_SAVE_PATH + "situation_awareness"}};
    DIR *isDir;
    int ret;
    for (string path : savePath)
    {
        isDir = opendir(path.c_str());
        if (isDir == NULL)
        {
            ret = mkdir(path.c_str(), 0755);
            if (ret != 0)
            {
                OH_LOG_ERROR(LOG_APP, "[ERROR] Create %{public}s failed", path.c_str());
            }
            else
            {
                OH_LOG_INFO(LOG_APP, "[INFO] Create %{public}s success", path.c_str());
            }
        }
        else
        {
            OH_LOG_INFO(LOG_APP, "[INFO] Path is existed: %{public}s", path.c_str());
        }
    }
}

/**
 * @brief Get the Connected Device Info object
 * 
 * @param deviceDetails Structure of devices detail
 * @param connectNum  Num of connected devices
 * @return string Assembled equipment information
 */
static string getConnectedDeviceInfo(DeviceDetail *deviceDetails, int connectNum)
{
    stringstream fmt;
    for (int i = 0; i < connectNum; i++)
    {
        fmt << "deviceName: " << deviceDetails[i].deviceName << "\n"
            << "ipAddress: " << deviceDetails[i].ipaddress << "\n"
            << "port: " << deviceDetails[i].port << ";\n";
    }
    OH_LOG_INFO(LOG_APP, "[HP_DEBUG] get device info success.");
    return fmt.str();
}

/**
 * @brief Callback function after receiving data
 * 
 * @param fileName Picture file name
 */
static void receiveCallback(string fileName)
{
    OH_LOG_INFO(LOG_APP, "[INFO] Ets callback is call");
    napi_value jsFileName, jsCallback = nullptr, jsResult = nullptr;
    napi_create_string_utf8(envRef, fileName.c_str(), fileName.length(), &jsFileName);

    napi_get_reference_value(envRef, callbackRef, &jsCallback);
    napi_call_function(envRef, nullptr, jsCallback, 1, &jsFileName, &jsResult);
    OH_LOG_INFO(LOG_APP, "[INFO] Ets callback end");
}

/**
 * @brief The napi encapsulation function of sessionServerInit
 * 
 * @param env Context pointer
 * @param info Callback function pointer
 * @return napi_value Napi data type
 */
static napi_value sessionServerInitC(napi_env env, napi_callback_info info)
{
    envRef = env;
    size_t argc = 1;
    napi_value args[argc];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    napi_valuetype callbackType = napi_undefined;
    napi_typeof(env, args[0], &callbackType);
    napi_create_reference(env, args[0], 1, &callbackRef);

    napi_value initResult;
    string result = sessionServerInit(receiveCallback);
    napi_create_string_utf8(env, result.c_str(), NAPI_AUTO_LENGTH, &initResult);
    return initResult;
}

/**
 * @brief The napi encapsulation function of sessionConnect
 * 
 * @param env Context pointer
 * @param info Callback function pointer
 * @return napi_value Napi data type
 */
static napi_value sessionConnectC(napi_env env, napi_callback_info info)
{
    napi_value connectResult;
    string result = sessionConnect();
    OH_LOG_INFO(LOG_APP, "[HP_DEBUG] get device info:%s", result.c_str());
    napi_create_string_utf8(env, result.c_str(), NAPI_AUTO_LENGTH, &connectResult);
    return connectResult;
}

/**
 * @brief The napi encapsulation function of sendData
 * 
 * @param env Context pointer
 * @param info Callback function pointer
 * @return napi_value Napi data type
 */
static napi_value sendDataC(napi_env env, napi_callback_info info)
{
    size_t bufferSize = 128;
    size_t copied;
    // Input quantity
    size_t argc = 3;
    // Declare parameter array
    napi_value args[3] = {nullptr};
    // Get the passed in parameters and put them into the parameter array in turn
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    // Determine the type of the first js parameter
    napi_valuetype valueType0;
    napi_typeof(env, args[0], &valueType0);
    // Determine the type of the second js parameter
    napi_valuetype valueType1;
    napi_typeof(env, args[1], &valueType1);
    // Determine the type of the third js parameter
    napi_valuetype valueType2;
    napi_typeof(env, args[2], &valueType2);
    // Convert the first incoming parameter to string type
    char value0Buffer[128];
    napi_get_value_string_utf8(env, args[0], value0Buffer, bufferSize, &copied);
    // Convert the second incoming parameter to the string type
    char value1Buffer[128];
    napi_get_value_string_utf8(env, args[1], value1Buffer, bufferSize, &copied);
    // Convert the third incoming parameter to type int
    int value2;
    napi_get_value_int32(env, args[2], &value2);
    // Call the function that sends the message
    napi_value sendResult;
    OH_LOG_INFO(LOG_APP, "[HP_DEBUG] before sendData value[%{public}s]", value1Buffer);\
    int result = sendData(value0Buffer, value1Buffer, value2);
    OH_LOG_INFO(LOG_APP, "[HP_DEBUG] after sendData value[%{public}s]", value1Buffer);
    napi_create_int32(env, result, &sendResult);

    return sendResult;
}

/**
 * @brief The napi encapsulation function of sessionDisconnect
 * 
 * @param env Context pointer
 * @param info Callback function pointer
 * @return napi_value Napi data type
 */
static napi_value sessionDisconnectC(napi_env env, napi_callback_info info)
{
    napi_value disconnectResult = nullptr;
    sessionDisconnect(0);
    return disconnectResult;
}

/**
 * @brief napi function registration
 * 
 * @param env Context pointer
 * @param exports 
 * @return EXTERN_C_START 
 */
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        {"sessionServerInit", nullptr, sessionServerInitC, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sessionConnect", nullptr, sessionConnectC, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sendData", nullptr, sendDataC, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sessionDisconnect", nullptr, sessionDisconnectC, nullptr, nullptr, nullptr, napi_default, nullptr},
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "libsoftbus_client_run",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    napi_module_register(&demoModule);
}
